程序代码要想运行在计算机之上,必须要转换为机器语言才行。而负责这项翻译工作的程序为编译器。与之不同的解释器不是通过翻译的方式生成目标程序,而是通过直接执行一行行程序代码来运行。
C 语言是编译型语言,Java 则结合了两种方式,一方面解释执行字节码,另一方面会采用 JIT 的方式将字节码编译为机器语言执行。
编译原理即是源程序映射为目标程序的原理和技术。从单词、空格组成的代码,转译为可以运行在 CPU 之上的机器代码,一般来说要经历以下流程。
词法分析
词法分析的目的是将字符流转换为符号流。以下述程序为例:
int add(int a) {
int b = a + 3;
return b;
}
其词法分析的效果如下:
生成的符号给到语法分析阶段使用。同时会生成对应符号表,用于后续的语义分析和代码生成阶段使用。
词法分析过程可以通过模拟有限自动机来实现,有限自动机问题又等价于正则表达式,所以我们又可以借助正则表达式来实现。
语法分析
语法分析的目的是将符号流转换为语法树。上述代码中表达式 b = a + 3
的转换效果如下。
该语法树后续用于分析源程序和生成目标程序。
语法分析需要使用上下文无关的文法来描述程序语法结构,是对正则文法(线性文法)的一次升级。
语义分析
语义分析的主要工作是根据语法树和符号表检查程序是否符合语义规则,同时收集一些类型信息更新到语法树和符号表中。这一部分工作是和上下文有关的,比如数组的下标必须为整数类型的校验等工作。
中间代码生成和优化
中间代码的生成主要目标是用于分析和优化或直接解释执行。比如 Java 字节码属于后者。中间代码 IR 的表示方式有很多种,语法树就是其中一种。
中间代码优化是为了得到性能更好的代码。可以采用诸如常数折叠、消除不可达代码等方法进行优化。
目标代码生成和优化
目标代码生成是指生成特定平台的机器代码。
首先要考虑特定平台不同指令的选择,要选择代码最低的指令进行翻译。然后由于 CPU 支持指令的流水线执行,所以我们可以对指令进行排序,以得到最佳的执行速度。另外还需要考虑对寄存器的分配问题,通过尽量访问寄存器而非内存来得到最快的读取速度。
总结
在源代码编译为目标代码过程中,我们一般分为两个阶段:分析和生成代码。其中分析部分我们称为前端,生成代码部分我们称为后端。前端包括:词法分析、语法分析、语义分析,用于得到程序的语法树和符号表;后端包括中间代码生成和优化、目标代码的生成和优化,根据语法树和符号表对代码进行优化和输出。